package parserxml;

import java.io.*;
import java.util.*;
import org.exolab.castor.xml.dtd.*;
import org.exolab.castor.xml.dtd.parser.*;

/**
 * @author Parra Jacob
 * @author Pimentel Victoria
 * @author Rodriguez Daniel
 * @author Velazquez Daniela
 */
public class Traductor {

    /**
     * Lista global con todas las tablas generadas a partir del DTD.
     */
    public HashMap<String, Tabla> listaTablas = new HashMap<String, Tabla>();
    public Writer logFile;
    /**
     * Lista global con todas las relaciones encontradas en el DTD.
     */
    public HashMap<String, RelacionER> relacionesER = new HashMap<String, RelacionER>();

    /**
     * Ejecuta los principales metodos para la elaboracion de la base
     * de datos a crear.
     * @param archivo nombre del archivo DTD.
     */
    public void ejecutarTraductorArchivo(String archivo) throws IOException {


        FileInputStream inputStream = null;
        InputStreamReader reader = null;
        InputCharStream charStream = null;
        DTDInitialParser initialParser = null;
        String intermedResult = null;
        StringReader strReader = null;
        DTDParser parser = null;
        DTDdocument dtd = null;

        try {
            inputStream = new FileInputStream(archivo);
            reader = new InputStreamReader(inputStream, "UTF-8");
            charStream = new InputCharStream(reader);
            initialParser = new DTDInitialParser(charStream);
            intermedResult = initialParser.Input();
            strReader = new StringReader(intermedResult);
            charStream = new InputCharStream(strReader);
            parser = new DTDParser(charStream);
            dtd = parser.Input();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        logFile = new FileWriter("archivoLog.txt");
        logFile.write(" \t**REGISTRO DE MODIFICACIONES REALIZADAS AL DTD ORIGINAL "
                + "PARA GARANTIZAR EL CORRECTO PROCESAMIENTO **\n\n");
        Enumeration enumelem = dtd.getElements();

        while (enumelem.hasMoreElements()) {
            Element e = (Element) enumelem.nextElement();
            procesarElemento(e, dtd);
        }
        //FUNCION PARA LEER EL XML Y GENERAR SCRIPT DE INSERCIONES
        GeneradorScriptInserciones esquemaScripter =
                new GeneradorScriptInserciones(listaTablas, relacionesER);
        //esquemaScripter.generarScript("contenido.xml");
    }

    /**
     * Ejecuta los principales metodos para la elaboracion de la base
     * de datos a crear.
     * @param dtd contenido de tipo DTD a ser considerado.
     */
    public void ejecutarTraductorCadena(String dtdcontenido) throws IOException {

        logFile = new FileWriter("archivoLog.txt");
        logFile.write(" \t**REGISTRO DE MODIFICACIONES REALIZADAS AL DTD ORIGINAL "
                + "PARA GARANTIZAR EL CORRECTO PROCESAMIENTO **\n\n");
        InputCharStream charStream = null;
        StringReader strReader = null;
        DTDParser parser = null;
        DTDdocument dtd = null;

        try {
            strReader = new StringReader(dtdcontenido);
            charStream = new InputCharStream(strReader);
            parser = new DTDParser(charStream);
            dtd = parser.Input();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        Enumeration enumelem = dtd.getElements();
        while (enumelem.hasMoreElements()) {
            Element e = (Element) enumelem.nextElement();
            procesarElemento(e, dtd);
        }
        //FUNCION PARA LEER EL XML Y GENERAR SCRIPT DE INSERCIONES
        //GeneradorScriptInserciones esquemaScripter =
        //new GeneradorScriptInserciones(listaTablas, relacionesER);
        //esquemaScripter.generarScript("contenido.xml");
    }

    /**
     * Muestra en pantalla una representacion simple del contenido de la
     * lista de tablas.
     */
    public void imprimirTablas() {

        Collection<Tabla> tablas = listaTablas.values();
        Iterator<Tabla> iterTabla = tablas.iterator();

        while (iterTabla.hasNext()) {

            Tabla tabla = iterTabla.next();
            System.out.println(tabla.getNombre() + ":");
            Iterator columnas = tabla.getAllAtributos();

            while (columnas.hasNext()) {
                Atributo c = (Atributo) columnas.next();
                System.out.println("    " + c.getNombre());
            }
        }
    }

    /**
     * Escribe en un archivo de nombre 'Schema.sql', un script
     * que genera todas las tablas guardadas en listaTablas (global).
     * @param nombreArchivo nombre del archivo que contendra el esquema SQL.
     * @throws IOException
     */
    public void generarSchema(String nombreArchivo) throws IOException {

        Writer out = new FileWriter(nombreArchivo);
        Collection<Tabla> tablas = listaTablas.values();
        Iterator<Tabla> iterTabla = tablas.iterator();

        while (iterTabla.hasNext()) {
            Tabla tabla = iterTabla.next();
            out.write("CREATE TABLE " + tabla.getNombre() + "(\n");

            Iterator<Atributo> iterAtributo = tabla.getAllAtributos();
            while (iterAtributo.hasNext()) {

                Atributo attr = iterAtributo.next();
                out.write("\t" + attr.getNombre() + " VARCHAR(255)");
                if (!attr.getIsNull()) {
                    out.write(" NOT NULL");
                }
                //Termina la linea del atributo con una coma y salto de linea
                out.write(",\n");
                //Si el atributo apunta a otra tabla entonces se construye
                //y agrega la restriccion al script
                if (attr.getClaveForanea() != null) {
                    out.write(processIDRefs(attr.getClaveForanea()));
                }
            }
            //Al final de la tabla se coloca la restriccion de la clave primaria
            if (!tabla.getClavePK().isEmpty()) {
                out.write("\tCONSTRAINT " + "pk_" + tabla.getNombre() + " PRIMARY KEY ("
                        + stringClaves(tabla.getClavePK())
                        + ")");
            }

            if (!tabla.getClavesFK().isEmpty()) {
                //Se le asigna a entidadesFK las entidades que hacen c foranea
                //junto con las claves
                HashMap<String, HashSet<String>> entidadesFK = tabla.getClavesFK();
                Iterator it_fk = entidadesFK.keySet().iterator();
                //Iteramos por las claves foraneas
                while (it_fk.hasNext()) {
                    String entidad = (String) it_fk.next();
                    HashSet<String> clavesfk = entidadesFK.get(entidad);
                    out.write(",\n\tFOREIGN KEY (" + stringClaves(clavesfk) 
                            + ") REFERENCES " + entidad + "(" + stringClaves(clavesfk) + ")\n");
                }
            } else {
                out.write("\n");
            }
            //Termina la tabla con parentesis y dos saltos de linea
            out.write(");\n\n");
        }
        out.close();
        logFile.close();
    }
    
    /**
     * Retorna un string que continen todas las tablas guardadas en 
     * listaTablas (global).
     * @return String con todo el esquema de las tablas
     * @throws IOException
     */
    public String generarSchema() throws IOException {

        String salida = "";
        Collection<Tabla> tablas = listaTablas.values();
        Iterator<Tabla> iterTabla = tablas.iterator();

        while (iterTabla.hasNext()) {
            Tabla tabla = iterTabla.next();
            salida += "CREATE TABLE " + tabla.getNombre() + "(\n";

            Iterator<Atributo> iterAtributo = tabla.getAllAtributos();
            while (iterAtributo.hasNext()) {

                Atributo attr = iterAtributo.next();
                salida += "\t" + attr.getNombre() + " VARCHAR(255)";
                if (!attr.getIsNull()) {
                    salida += " NOT NULL";
                }
                //Termina la linea del atributo con una coma y salto de linea
                salida += ",\n";
                //Si el atributo apunta a otra tabla entonces se construye
                //y agrega la restriccion al script
                if (attr.getClaveForanea() != null) {
                    salida += processIDRefs(attr.getClaveForanea());
                }
            }
            //Al final de la tabla se coloca la restriccion de la clave primaria
            if (!tabla.getClavePK().isEmpty()) {
                salida += "\tCONSTRAINT "+ "pk_" + tabla.getNombre() +" PRIMARY KEY ("
                        + stringClaves(tabla.getClavePK())
                        + ")";
            }

            if (!tabla.getClavesFK().isEmpty()) {
                //Se le asigna a entidadesFK las entidades que hacen c foranea
                //junto con las claves
                HashMap<String, HashSet<String>> entidadesFK = tabla.getClavesFK();
                Iterator it_fk = entidadesFK.keySet().iterator();
                //Iteramos por las claves foraneas
                while (it_fk.hasNext()) {
                    String entidad = (String) it_fk.next();
                    HashSet<String> clavesfk = entidadesFK.get(entidad);
                    salida += ",\n\tFOREIGN KEY (" + stringClaves(clavesfk) 
                            + ") REFERENCES " + entidad + "(" + stringClaves(clavesfk) + ")\n";
                }
            }
            else salida += "\n";
            //Termina la tabla con parentesis y dos saltos de linea
            salida += ");\n\n";
        }
        logFile.close();
        return salida;
    }
    
    /**
     * Dado un conjunto de claves las procesa y junta en una sola cadena,
     * separadas por coma, para poder ser incluidas adecuadamente en el esquema
     * SQL.
     * @param claves
     * @return clavesString cadena formada por las claves separadas por coma.
     */
    private String stringClaves(HashSet<String> claves) {

        Iterator<String> iteradorClaves = claves.iterator();
        String clavesString = iteradorClaves.next();

        while (iteradorClaves.hasNext()) {
            clavesString += ", " + iteradorClaves.next();
        }
        return clavesString;
    }

    /**
     * Evalua si el elemento e es una entidad o una relacion para ser procesado
     * como tal.
     * @param e Elemento a ser procesado.
     * @param dtd Documento que esta siendo procesado.
     */
    private void procesarElemento(Element e, DTDdocument dtd) throws IOException {

        if (esEntidad(dtd, e) && !yaEstaCreada(e)) {
            procesarEntidad(dtd, e);
        } else {
            int[] relacion = new int[2];
            relacion = esRelacion(dtd, e);
            /*Procesa*/
            if (relacion[0] == 1 || relacion[0] == -1) {
                procesarRelacion(e, dtd, relacion[1]);
            }
            /* else logFile.write("El elemento \""+e.getName()+"\" no fue considerado"
            + " en el esquema pues no cumple con los requerimientos de"
            + " una entidad , relacion o atributo\n");*/
        }
    }

    /**
     * Procesa el elemento e como una relacion, evalua que tipo de relacion
     * es, tomando en cuenta su cardinalidad, para ser procesado como tal.
     * @param e Elemento a ser procesado.
     * @param dtd Documento que esta siendo procesado.
     */
    private void procesarRelacion(Element e, DTDdocument dtd, int cantHijos) throws IOException {

        RelacionER r = new RelacionER(e.getName());
        String[] cardinalidad = new String[cantHijos];
        cardinalidad = cardinalidadRelacion(e, dtd, r, cantHijos);

        int j;
        for (j = 0; j < cantHijos; j++) {
            if (cardinalidad[j].equals("(1,1)")) {
                relacion11(e, dtd, r);
                break;
            }
        }

        if (j == cantHijos) {
            relacion0N(e, dtd, r);
        }

        relacionesER.put(e.getName(), r);

    }

    /**
     * Procesa el elemento e como una relacion para obtener su cardinadad y la
     * agrega con todas sus entidades participantes a la lista de relaciones.
     * @param e Elemento a ser procesado.
     * @param dtd Documento que esta siendo procesado.
     * @return cardRelacion arreglo de string formado por las entidades
     * participantes en la relacion.
     */
    private String[] cardinalidadRelacion(Element e, DTDdocument dtd,
            RelacionER r, int cantHijos) {

        ContentParticle cp = e.getContent();
        Enumeration hijosrel = cp.getChildren();
        String[] cardRelacion = new String[cantHijos];
        String nombrehijo;
        int i = 0;

        while (hijosrel.hasMoreElements()) {
            ContentParticle cphijos = (ContentParticle) hijosrel.nextElement();
            nombrehijo = cphijos.getReference();
            Element hijo = dtd.getElement(nombrehijo);

            if (esEntidad(dtd, hijo)) {
                if (cphijos.isZeroOrMoreOccurances()) {
                    cardRelacion[i] = "(0,N)";
                    r.addEntidad(nombrehijo, r.MINMAX_0N);
                } else if (cphijos.isOneOrMoreOccurances()) {
                    cardRelacion[i] = "(1,N)";
                    r.addEntidad(nombrehijo, r.MINMAX_1N);
                } else if (cphijos.isZeroOrOneOccurance()) {
                    cardRelacion[i] = "(0,1)";
                    r.addEntidad(nombrehijo, r.MINMAX_01);
                } else if (cphijos.isOneOccurance()) {
                    cardRelacion[i] = "(1,1)";
                    r.addEntidad(nombrehijo, r.MINMAX_11);
                }
                //i++;
            } else {
                cardRelacion[i] = "0";
            }
            i++;
        }
        return cardRelacion;
    }

    /**
     * Procesa el elemento e como una relacion de cardinalidad 1:1.
     * @param e Elemento a ser procesado.
     * @param dtd Documento que esta siendo procesado.
     */
    private void relacion11(Element relacion, DTDdocument dtd, RelacionER r) throws IOException {

        ContentParticle cp = relacion.getContent();
        Enumeration hijosrel = cp.getChildren();
        String nombrehijo = "";
        Element entidad1 = null;
        boolean passed = false;
        ArrayList<ContentParticle> atributosrel = new ArrayList<ContentParticle>();

        while (hijosrel.hasMoreElements()) {
            ContentParticle cphijo = (ContentParticle) hijosrel.nextElement();
            nombrehijo = cphijo.getReference();
            Element element = dtd.getElement(nombrehijo);
            //System.out.println(element.getName());
            if (esEntidad(dtd, element)) {
                if (cphijo.isOneOccurance() && passed == false) {
                    entidad1 = dtd.getElement(nombrehijo);
                    passed = true;
                }
            } else {
                atributosrel.add(cphijo);
            }
        }

        if (!yaEstaCreada(entidad1)) {
            procesarEntidad(dtd, entidad1);
        }

        Tabla e1 = listaTablas.get(entidad1.getName());
        for (int i = 0; i < atributosrel.size(); i++) {
            procesarAtributo(dtd, e1, atributosrel.get(i), 0);
            if (atributosrel.get(i).isZeroOrMoreOccurances()
                    || atributosrel.get(i).isZeroOrOneOccurance()) {
                r.addAtributo(atributosrel.get(i).getReference(), false);
            } else {
                r.addAtributo(atributosrel.get(i).getReference(), true);
            }
        }

        cp = relacion.getContent();
        hijosrel = cp.getChildren();
        while (hijosrel.hasMoreElements()) {
            ContentParticle cphijo = (ContentParticle) hijosrel.nextElement();
            nombrehijo = cphijo.getReference();
            Element element = dtd.getElement(nombrehijo);
            if (!element.getName().equals(entidad1.getName())) {
                if (!yaEstaCreada(element)) {
                    procesarEntidad(dtd, element);
                }
                agregar(entidad1, element, r);
            }
        }

    }

    /**
     * Agrega a la entidad1, las entidades que absorbe debido a su participacion
     * en la relacion.
     * @param entidad1 Elemento absorbedor.
     * @param entidad2 Elemento que absorbe.
     * @param r Relacion siendo procesada.
     */
    private void agregar(Element entidad1, Element entidad2, RelacionER r) {

        HashSet<String> pk2 = new HashSet<String>();
        Tabla e1 = listaTablas.get(entidad1.getName());
        Tabla e2 = listaTablas.get(entidad2.getName());
        pk2 = e2.getClavePK();
        Iterator<String> iterator = pk2.iterator();
        while (iterator.hasNext()) {
            String ad = iterator.next();
            Atributo at = new Atributo(ad, false);
            e1.addAtributo(ad, at);
        }
        e1.addClavesFK(entidad2.getName(), pk2);
        r.setAbsorbida(entidad1.getName());


    }

    /**
     * Procesa el elemento e como una relacion de cardinalidad 0:N.
     * @param e Elemento a ser procesado
     * @param dtd Documento que esta siendo procesado
     */
    private void relacion0N(Element relacion, DTDdocument dtd, RelacionER r) throws IOException {

        Tabla tabla = new Tabla(relacion.getName());
        listaTablas.put(relacion.getName(), tabla);
        ContentParticle cp = relacion.getContent();
        Enumeration hijosrelacion = cp.getChildren();
        String nombrehijo;
        HashSet<String> clavesForaneas = new HashSet<String>();

        while (hijosrelacion.hasMoreElements()) {

            ContentParticle cphijo = (ContentParticle) hijosrelacion.nextElement();
            nombrehijo = cphijo.getReference();
            Element hijo = dtd.getElement(nombrehijo);

            if (esEntidad(dtd, hijo)) {
                procesarEntidad(dtd, hijo);
                Tabla e1 = listaTablas.get(nombrehijo);
                HashSet<String> clave = e1.getClavePK();
                tabla.addClavesFK(nombrehijo, clave);
                Iterator<String> it = clave.iterator();
                while (it.hasNext()) {
                    String e1Clave = it.next();
                    Atributo e1Attr = new Atributo(e1Clave, false);
                    tabla.addAtributo(e1Clave, e1Attr);
                    clavesForaneas.add(e1Clave);
                }
            } else {
                if (cphijo.isZeroOrMoreOccurances()
                        || cphijo.isZeroOrOneOccurance()) {
                    r.addAtributo(nombrehijo, true);
                } else {
                    r.addAtributo(nombrehijo, false);
                }
                procesarAtributo(dtd, tabla, cphijo, 0);
            }
        }

        tabla.setClavePK(clavesForaneas);
    }

    /***
     * Procesa el elemento e como una entidad.
     * @param e Elemento a ser procesado
     */
    private void procesarEntidad(DTDdocument dtd, Element e) throws IOException {

        if (!yaEstaCreada(e)) {
            Tabla tabla = new Tabla(e.getName());
            listaTablas.put(e.getName(), tabla);
            procesarAttlist(e.getAttributes(), tabla);

            if (tieneHijos(e)) {
                if (e.getContent().isChoiceType() || e.getContent().isSeqType()) {
                    Enumeration hijos = e.getContent().getChildren();
                    while (hijos != null && hijos.hasMoreElements()) {
                        ContentParticle hijo = (ContentParticle) hijos.nextElement();
                        //System.out.println("prcesaratr con " + tabla.getNombre() + " " +hijo.getReference());
                        procesarAtributo(dtd, tabla, hijo, 0);
                    }

                } else {
                    ContentParticle hijo = e.getContent();
                    //System.out.println("prcesaratr con " + tabla.getNombre() + " " +hijo.getReference());
                    procesarAtributo(dtd, tabla, hijo, 0);
                }
            }
        }
    }

    /**
     * Evalua si la tabla correspondiente al elemento e ya ha sido creada.
     * @param e Elemento
     * @return true si la tabla para el elemento e ya ha sido creada
     * @return false si la tabla para el elemento e no ha sido creada
     */
    private boolean yaEstaCreada(Element e) {

        String nombre = e.getName();
        if (listaTablas.keySet().contains(nombre)) {
            return true;
        }

        return false;
    }

    /**
     * Procesa los atributos y los agrega a una tabla.
     * @param dtd Documento que esta siendo procesado
     * @param tabla Tabla sobre la cual se insertaran los atributos
     * @param attr ContentParticle a ser insertado como atributo
     */
    private void procesarAtributo(DTDdocument dtd, Tabla tabla, ContentParticle attr, int cont) throws IOException {

        Atributo atributo;
        //System.out.println("tabla es:" + tabla.getNombre());
        //System.out.println("attr es:" + attr.getReference());
        if (attr.isZeroOrOneOccurance()) {
            atributo = new Atributo(attr.getReference(), true);
            tabla.addAtributo(attr.getReference(), atributo);

            // Si el atributo es compuesto lo agrega en la tabla de la manera
            // adecuada.
            if (esCompuesto(dtd, attr)) {
                LinkedList aplaname = aplanarAtributo(dtd, tabla, attr, cont);
                agregarCompuestoAplanado(aplaname, tabla, attr);
            }

        } else if (attr.isOneOccurance()) {
            atributo = new Atributo(attr.getReference(), false);
            tabla.addAtributo(attr.getReference(), atributo);
            // Si el atributo es compuesto lo agrega en la tabla de la manera
            // adecuada.
            if (esCompuesto(dtd, attr)) {
                LinkedList aplaname = aplanarAtributo(dtd, tabla, attr, cont);
                agregarCompuestoAplanado(aplaname, tabla, attr);
            }
        } else if (attr.isOneOrMoreOccurances() || attr.isZeroOrMoreOccurances()) {
            if (cont >= 1) {
                logFile.write("WARNING: Hay un atributo compuesto que posee dos o mas "
                        + "niveles de profundidad por lo que no se asegura"
                        + " la correcta creacion de las tablas involucradas."
                        + " Entidades involucradas: \"" + attr.getReference() + "\".\n");
            }

            Element atelem = dtd.getElement(attr.getReference());
            Tabla tablaAtributo = null;
            HashSet<String> tablaAtributoClaves = new HashSet<String>();
            if (yaEstaCreada(atelem)) {
                tablaAtributo = new Tabla(attr.getReference().concat("_").concat(tabla.getNombre()));
                logFile.write("WARNING: Nombre de tabla ya existente: Se cambio el nombre del atributo \""
                        + attr.getReference() + "\" de la tabla \"" + tabla.getNombre()
                        + "\" por \"" + attr.getReference().concat("_").concat(tabla.getNombre()) + "\".\n");
            } else {
                tablaAtributo = new Tabla(attr.getReference());
            }
            //tablaAtributoClaves = (HashSet<String>) tabla.getClavePK().clone();
            Iterator iterator = tabla.getClavePK().iterator();
            while (iterator.hasNext()) {
                String nombre = new String((String) iterator.next());
                // System.out.println("kskls "+tablaAtributoClaves);
                tablaAtributoClaves.add(nombre);
            }

            atributo = new Atributo(attr.getReference(), false);
            LinkedList<Atributo> listaClavesPadre = tabla.pkList();
            Iterator clavesPadreIter = listaClavesPadre.iterator();
            //Agrega todas las claves del padre.
            while (clavesPadreIter.hasNext()) {
                Atributo attr_aux = (Atributo) clavesPadreIter.next();
                tablaAtributo.addClavesFK(tabla.getNombre(), tabla.getClavePK());
                tablaAtributo.addAtributo(attr_aux.getNombre(), attr_aux);
            }
            //Multivaluados compuestos.
            if (esCompuesto(dtd, attr)) {
                LinkedList aplaname = aplanarAtributo(dtd, tabla, attr, cont);
                Iterator iter = aplaname.iterator();
                agregarCompuestoAplanado(aplaname, tablaAtributo, attr);

                //Agregar los elementos del atributo compuesto como clave
                //primaria compuesta de la tabla nueva.
                while (iter.hasNext()) {
                    Atributo a = (Atributo) iter.next();
                    String n = a.getNombre();
                    tablaAtributoClaves.add(n);
                }
                Iterator i = tablaAtributoClaves.iterator();
                tablaAtributo.setClavePK(tablaAtributoClaves);
            } //Multivaluados simples.
            else {
                tablaAtributoClaves.add(attr.getReference());
                tablaAtributo.addAtributo(attr.getReference(), atributo);
                tablaAtributo.setClavePK(tablaAtributoClaves);
                tablaAtributo.addClavePK(attr.getReference());
            }
            listaTablas.put(tablaAtributo.getNombre(), tablaAtributo);

        }

    }

    /**
     * Agrega los atributos compuestos a la tabla correspondiente.
     * @param Una lista de atributos aplanados.
     * @param Una tabla a la cual agregar los atributos.
     */
    private void agregarCompuestoAplanado(LinkedList aplaname, Tabla tabla, ContentParticle attr) {

        if (!aplaname.isEmpty()) {
            //Eliminar atributo padre de la tabla
            tabla.removeAtributo(attr.getReference());
            Iterator it = aplaname.iterator();
            while (it.hasNext()) {
                Atributo athijo = (Atributo) it.next();
                Atributo atributo = new Atributo(athijo.getNombre(), true);
                tabla.addAtributo(athijo.getNombre(), atributo);
            }
        }
    }

    /**
     * Procesa los hijos de un atributo y los agrega la tabla.
     * @param dtd Documento que esta siendo procesado.
     * @param tabla Tabla sobre la cual se insertaran los atributos.
     * @param contPar ContentParticle a ser aplanado.
     * @return compuesto una lista con todos los atributos que hay sido
     * aplanados y que deben ahora ser agregados a la tabla.
     */
    private LinkedList aplanarAtributo(DTDdocument dtd, Tabla tabla, ContentParticle contPar, int cont) throws IOException {

        LinkedList<Atributo> compuesto = new LinkedList<Atributo>();
        Atributo attr;

        Element e = dtd.getElement(contPar.getReference());
        if (e.isElemOnlyContent()) {
            ContentParticle cp = e.getContent();
            boolean nulo = false;
            if (tieneHijos(cp)) {
                Enumeration children = cp.getChildren();
                while (children.hasMoreElements()) {
                    ContentParticle child = (ContentParticle) children.nextElement();
                    Element hijo = dtd.getElement(child.getReference());
                    if (hijo.isMixedContent()) {
                        if (child.isZeroOrMoreOccurances() || child.isZeroOrOneOccurance()) {
                            nulo = true;
                        }
                        attr = new Atributo(child.getReference(), nulo);
                        compuesto.add(attr);
                    } else if (hijo.isElemOnlyContent()) {
                        ContentParticle cphijo = hijo.getContent();
                        if (cphijo.isSeqType() || cphijo.isChoiceType()) {
                            Enumeration h = cphijo.getChildren();
                            while (h.hasMoreElements()) {
                                ContentParticle cp2 = (ContentParticle) h.nextElement();
                                procesarAtributo(dtd, tabla, cp2, ++cont);
                            }
                        } else {
                            procesarAtributo(dtd, tabla, cphijo, ++cont);
                        }
                    }
                }

            } else {
                attr = new Atributo(cp.getReference(), false);
                compuesto.add(attr);
            }

        }
        return compuesto;
    }

    /**
     * Evalua si un elemento e es un atributo compuesto.
     * @param e Elemento a ser evaluado
     * @param dtd Documento sobre el cual se evaluara
     * @return true si es un atributo compuesto
     * @return false si no es un atributo compuesto
     */
    private boolean esCompuesto(DTDdocument dtd, Element e) {

        if (esHijo(dtd, e) && tieneHijos(e)) { //OJOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO

            if (!esEntidad(dtd, e) || esPcdata(e)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Evalua si un ContentParticle cp corresponde a un atributo compuesto.
     * @param dtd Documento sobre el cual se evaluara
     * @param cp ContentParticle a ser evaluado
     * @return true si es un atributo compuesto
     * @return false si no es un atributo compuesto
     */
    private boolean esCompuesto(DTDdocument dtd, ContentParticle cp) {

        if (cp.isReferenceType()) {
            Element e = dtd.getElement(cp.getReference());
            return esCompuesto(dtd, e);
        }
        return false;
    }

    /**
     * Evalua si un ContentParticle cp corresponde a un atributo simple.
     * @param dtd Documento sobre el cual se evaluara
     * @param cp ContentParticle a ser evaluado
     * @return true si es un atributo simple
     * @return false si no es un atributo simple
     */
    private boolean esAtributoSimple(DTDdocument dtd, ContentParticle cp) {

        Element e;

        if (cp.isReferenceType() && !tieneHijos(cp)) { //OJOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
            e = dtd.getElement(cp.getReference());
            if (esEntidad(dtd, e) || tieneId(e)) {
                return false;

            } else if (esPcdata(cp)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Evalua si un elemento e tiene hijos, es decir no es PCDATA si no
     * que contiene a otros elementos.
     * @param e Elemento a ser evaluado
     * @return retorna true si el elemento e tiene hijos
     * @return retorna false si el elemento e no tiene hijos
     */
    private boolean tieneHijos(Element e) {

        if (e.isElemOnlyContent()) {
            return true;
        }

        return false;
    }

    /**
     * Evalua si un ContentParticle cp tiene hijos, es decir no es PCDATA si no
     * que contiene a otros elementos.
     * @param cp ContentParticle a ser evaluado
     * @return retorna true si el elemento e tiene hijos
     * @return retorna false si el elemento e no tiene hijos
     */
    private boolean tieneHijos(ContentParticle cp) {

        if (cp.isSeqType() || cp.isChoiceType()) {
            return true;
        }

        return false;
    }

    /**
     * Revisa si el elemento e posee un ATTLIST con ID.
     * @param e Elemento a ser revisado
     * @return true si el elemento e tiene id
     * @return false si el elemento e no tiene id
     */
    private boolean tieneId(Element e) {

        Enumeration atributos = e.getAttributes();

        while (atributos.hasMoreElements()) {
            Attribute atributo = (Attribute) atributos.nextElement();
            if (atributo.isIDType()) {
                return true;
            }
        }

        return false;

    }

    /**
     * Evalua si el elemento e cumple con las caracteristicas de una entidad:
     * Tiene ID y ninguno de sus hijos tiene ID.
     * @param e Element
     * @return true si el elemento cumple con las reglas de una entidad
     * @return false si el elemento no cumple con las reglas de una entidad
     */
    private boolean esEntidad(DTDdocument dtd, Element e) {

        if (tieneId(e) && tieneHijos(e)) {
            Enumeration hijos = e.getContent().getChildren();
            while (hijos != null && hijos.hasMoreElements()) {
                ContentParticle hijoParticle = (ContentParticle) hijos.nextElement();
                if (hijoParticle.isReferenceType()) {
                    Element hijo = dtd.getElement(hijoParticle.getReference());
                    if (esEntidad(dtd, hijo)) {
                        return false;
                    }
                }
            }
            return true;
        }

        return false;

    }

    /**
     * Actualiza la tabla del elemento correspondiente agregando todos
     * los atributos de su ATTLIST.
     * @param attributes Enumeration (lista de atributos)
     * @param tabla Tabla sobre la cual se insertaran los nuevos elementos
     */
    private void procesarAttlist(Enumeration attributes, Tabla tabla) {

        Atributo atributo;
        Attribute attrDTD;

        //Recorre todos los atributos.
        while (attributes.hasMoreElements()) {
            attrDTD = (Attribute) attributes.nextElement();
            atributo = new Atributo(attrDTD.getName(), attrDTD.isIMPLIED());
            if (attrDTD.isIDType()) {
                tabla.addClavePK(attrDTD.getName());
            }

            if (attrDTD.isREQUIRED()) {
                atributo.setIsNull(false);
            }

            tabla.addAtributo(attrDTD.getName(), atributo);
        }

    }

    /***
     * I HAVE NO IDEA QUE HACE ESTO
     * @param un String correspondiente a la clave foranea.
     * @return un String correspondiente a la declaracion de una
     * clave foranea en el scrip sql.
     */
    private String processIDRefs(String idRefName) {

        Tabla tabla = listaTablas.get(idRefName);
        String claveFK;

        claveFK = "\tFOREIGN KEY (fk_"+ idRefName +"_"+stringClaves(tabla.getClavePK())
                + "REFERENCES " + idRefName + "("
                + stringClaves(tabla.getClavePK()) + ", "
                + ")\n";

        return claveFK;

    }

    /**
     * Verifica si elemento e es hijo de algun otro elemento en el archivo dtd.
     * @param dtd Documento sobre el cual se realizara la verificacion
     * @param e Elemento a ser evaluado
     * @return true si el elemento e es hijo de algun otro elemento
     * @return false si el elemento e no es hijo de algun otro elemento
     */
    private boolean esHijo(DTDdocument dtd, Element e) {

        Enumeration elements = dtd.getElements();

        //Recorre todos los elementos del dtd.
        while (elements.hasMoreElements()) {
            Element element = (Element) elements.nextElement();
            //Si tiene hijos que son elementos.
            if (element.isElemOnlyContent()) {

                ContentParticle cpAux = element.getContent();
                //Si tiene varios hijos.
                if (cpAux.isSeqType() || cpAux.isChoiceType()) {
                    Enumeration children = cpAux.getChildren();
                    //Recorre los hijos y comparo los nombres.
                    while (children.hasMoreElements()) {
                        ContentParticle child = (ContentParticle) children.nextElement();
                        if (child.isReferenceType() && child.getReference().equals(e.getName())) {
                            return true;
                        }
                    }
                } //Si tiene solo un hijo comparo los nombres.
                else if (cpAux.isReferenceType() && cpAux.getReference().equals(e.getName())) {
                    return true;
                }
            }
        }

        return false;

    }

    /**
     * Evalua si el elemento e es de tipo PCDATA.
     * @param e Elemento a ser evaluado
     * @return true si el elemento es de tipo PCDATA
     * @return false si el elemento no es de tipo PCDATA
     **/
    private boolean esPcdata(Element e) {

        return e.isMixedContent();

    }

    /**
     * Evalua si el ContentParticle cp es de tipo PCDATA.
     * @param cp ContentParticle a ser evaluado
     * @return true si el ContentParticle es de tipo PCDATA
     * @return false si el ContentParticle no es de tipo PCDATA
     **/
    private boolean esPcdata(ContentParticle cp) {

        return !cp.isChoiceType() && !cp.isSeqType();

    }

    /**
     * Evalua si el elemento e cumple con las caracteristicas de una relacion:
     * No tiene padre, no tiene ID y sus hijos son entidades o de tipo PCDATA.
     * @param e Element
     * @return true si el elemento cumple con las reglas de una relacion
     * @return false si el elemento no cumple con las reglas de una relacion
     */
    private int[] esRelacion(DTDdocument dtd, Element e) throws IOException {

        int cont = 0;
        String nombre = "";
        int[] isRelacion = new int[2];
        isRelacion[0] = 0;
        HashMap<ContentParticle, Element> atribMod = new HashMap<ContentParticle, Element>();


        if (!esHijo(dtd, e) && !esEntidad(dtd, e)) {

            //Si tiene mas de 1 hijo
            if (e.isElemOnlyContent()) {
                int cantidadHijos = 0;
                ContentParticle cp = e.getContent();
                if (cp.isSeqType() || cp.isChoiceType()) {
                    Enumeration enu = cp.getChildren();
                    while (enu.hasMoreElements()) {
                        cantidadHijos++;
                        ContentParticle conpar = (ContentParticle) enu.nextElement();
                        nombre = conpar.getReference();
                        Element elem = dtd.getElement(nombre);
                        if (esEntidad(dtd, elem)) {
                            cont++;
                        } else if (!esEntidad(dtd, elem)) {
                            if (!esPcdata(elem)) {
                                return isRelacion;
                            }
                            //Si tiene * o + modifico ocurrencia
                            if (conpar.isOneOrMoreOccurances()) {
                                isRelacion[0] = -1;
                                atribMod.put(conpar, elem);
                            }
                            if (conpar.isZeroOrMoreOccurances()) {
                                isRelacion[0] = -1;
                                atribMod.put(conpar, elem);
                            }
                        }
                    }
                    //Si tiene mas de 2 hijos entidades
                    if (cont >= 2) {

                        /*Escribe en el archivo log que los atributos del ATTLIST seran obviados*/
                        Enumeration enu1 = e.getAttributes();
                        if (enu1 != null) {
                            while (enu1.hasMoreElements()) {
                                Attribute attr = (Attribute) enu1.nextElement();

                                logFile.write("WARNING: En la relacion \"" + e.getName()
                                        + "\" se omitio el atributo \"" + attr.getName()
                                        + "\" pues no se procesan atributos del ATTLIST.\n");
                            }
                        }




                        isRelacion[1] = cantidadHijos;
                        if (isRelacion[0] == -1) {
                            Set enumAtrib = atribMod.keySet();
                            Iterator en = enumAtrib.iterator();
                            while (en.hasNext()) {
                                ContentParticle contentP = (ContentParticle) en.next();
                                if (contentP.isOneOrMoreOccurances()) {
                                    contentP.setOneOccurance();
                                    logFile.write("WARNING: En la relacion \"" + e.getName()
                                            + "\" se modifico la ocurrencia del"
                                            + " atributo \"" + contentP.getReference() + "\" de "
                                            + "1 o mas ocurrencias a 1 ocurrencia.\n");
                                }
                                if (contentP.isZeroOrMoreOccurances()) {
                                    contentP.setZeroOrOneOccurance();
                                    logFile.write("WARNING: En la relacion \"" + e.getName()
                                            + "\" se modifico la ocurrencia del"
                                            + " atributo \"" + contentP.getReference() + "\" de "
                                            + "0 o mas ocurrencias a 0 o 1 ocurrencia.\n");
                                }
                            }
                        } else {
                            isRelacion[0] = 1;
                        }
                    }
                }
            }
        }
        return isRelacion;
    }
}
